package com.googlecode.perfectable.eventable.routing;

import java.util.Map;

import com.google.common.collect.ImmutableMap;

public class DispatchingSink<M> implements Sink<M> {
	
	public enum MissedTypeStrategy {
		THROWING {
			@Override
			void handle(Object element) {
				throw new IllegalStateException("Unconfigured sink for element " + element);
			}
		},
		IGNORING {
			@Override
			void handle(Object element) {
				// does nothing
			}
		};
		
		abstract void handle(Object element);
	}
	
	public static <M> DispatchingSink<M> create(MissedTypeStrategy missedStrategy) {
		Map<Class<? extends M>, ? extends Sink<? extends M>> dispatchMap = ImmutableMap.of();
		return new DispatchingSink<>(missedStrategy, dispatchMap);
	}
	
	public <E extends M> DispatchingSink<M> dispatch(Class<E> dispatchedType, Sink<E> dispatchedSink) {
		@SuppressWarnings("hiding")
		Map<Class<? extends M>, ? extends Sink<? extends M>> dispatchMap =
				ImmutableMap.<Class<? extends M>, Sink<? extends M>> builder()
						.putAll(this.dispatchMap)
						.put(dispatchedType, dispatchedSink)
						.build();
		return new DispatchingSink<>(this.missedStrategy, dispatchMap);
	}
	
	private final MissedTypeStrategy missedStrategy;
	private final Map<Class<? extends M>, ? extends Sink<? extends M>> dispatchMap;
	
	private DispatchingSink(MissedTypeStrategy missedStrategy,
			Map<Class<? extends M>, ? extends Sink<? extends M>> dispatchMap) {
		this.missedStrategy = missedStrategy;
		this.dispatchMap = dispatchMap;
	}
	
	@Override
	public void receive(M event) {
		@SuppressWarnings("unchecked")
		Class<? extends M> eventClass = (Class<? extends M>) event.getClass();
		Sink<? extends M> target = this.dispatchMap.get(eventClass);
		if(target == null) {
			this.missedStrategy.handle(event);
			return;
		}
		@SuppressWarnings("unchecked")
		final Sink<M> castedTarget = (Sink<M>) target;
		castedTarget.receive(event);
	}
	
}
